Source file: /~heha/enas/STS Multiplex/firmware.zip/usb.cpp

#include "usb.h"
#include <util/delay.h>

#define EP0_SIZE 64

/****************
 * Deskriptoren *
 ****************/

#define W(x) (x)&0xFF,(x)>>8
#define D(x) W(x&0xFFFF),W(x>>16)
static const byte PROGMEM HidReportDesc[] = {
// Erstes HID-Gerät: USB-Serial-Konverter ohne Strings
 0x06, W(0xFFA0),	// G Usage Page (0xFFA0 = USB-seriell wie HE2325U)
 0x09, 0x03,		// L Usage
 0xA1, 0x01,		// M Collection (Application)
// SerialCfgReport: 4 Byte Feature
  0x85, 4,		// G  Report ID (4)
  0x25, 1,		// G  Logical Maximum (1)
  0x09, 0x21,		// L  Usage (Baudrate)
  0x15, 0,		// G  Logical Minimum (0)
  0x27, D(2000000),	// G  Logical Maximum (2000000)
  0x75, 32,		// G  Report Size (32 Bit)
  0x95, 1,		// G  Report Count (1 Word)
  0xB1, 0x02,		// M  Feature (Var,NoPreferred)
  0x09, 0x22,		// L  Usage (8255-kompatible Register)
  0x26, W(255),		// G  Logical Maximum (255)
  0x75, 8,		// G  Report Size (8 bit)
  0x95, 5,		// G  Report Count (5 Byte)
  0xB1, 0xA2,		// M  Feature (Var,NoPreferred,Volatile)
// SerialInReport: 64 Byte In
  0x05, 1,		// G  Usage Page: Generic Desktop
  0x85, 5,		// G  Report ID (5)
  0x09, 0x3B,		// L  Usage (Byte Count)
  0x25, 62,		// G  Logical Maximum (62)
  0x75, 8,		// G  Report Size (8 Bit)
  0x95, 1,		// G  Report Count (1 Byte)
  0x81, 0x02,		// M  Input (Var,NoPreferred)
  0x09, 0x3A,		// L  Usage (Counted Buffer)
  0x26, W(255),		// G  Logical Maximum (255)
  0x95, 62,		// G  Report Count (62 Byte)
  0x82, W(0x102),	// M  Input (Var,NoPreferred,BufBytes)
// SerialOutReport: 64 Byte Out
  0x85, 6,		// G  Report ID (6)
  0x09, 0x3B,		// L  Usage (Byte Count)
  0x25, 62,		// G  Logical Maximum (62)
  0x95, 1,		// G  Report Count (1 Byte)
  0x91, 0x02,		// M  Output (Var,NoPreferred)
  0x09, 0x3A,		// L  Usage (Counted Buffer)
  0x26, W(255),		// G  Logical Maximum (255)
  0x95, 62,		// G  Report Count (62 Byte)
  0x92, W(0x102),	// M  Output (Var,NoPreferred,BufBytes)
 0xC0,			// M End Collection
// Zweites HID-Gerät: PnP-Sensor/Aktor mit Strings
 0x06, W(0xFF00),	// G Usage Page (0xFF00)
 0x09, 0x03,		// L Usage
 0xA1, 0x01,		// M Collection (Application)
// DioReport: 2 Byte Feature
  0x85, 3,		// G  Report ID (3)
  0x15, 0,		// G  Logical Minimum (0)
  0x25, 1,		// G  Logical Maximum (1)
  0x75, 1,		// G  Report Size (1 Bit)
  0x95, 8,		// G  Report Count (8 Bit)
  0x09, 8,		// L  Usage (Ausgabebit)
  0x79, 8,		// L  String (8 = "Digitaler npn-Ausgang")
  0xB1, 0x02,		// M  Feature (Var,NoPreferred)
// AdcReport: 18 Byte Feature
  0x85, 1,		// G  Report ID (1)
  0x09, 4,		// L  Usage (Flags)
  0x79, 4,		// L  String (4 = "Neuer Wert")
  0xB1, 0xA2,		// M  Feature (Var,NoPreferred,Volatile)
  0x27, D(0xFFC0L),	// G  Logical Maximum (65472 = 64*0x3FF)
  0x75, 16,		// G  Report Size (16 Bit)
  0x09, 5,		// L  Usage (Istwert)
  0x79, 5,		// L  String (5 = "Istwert")
  0x67, D(0xF0D121),	// G  Unit (Spannung in Volt)
  0x55, 0x0D,		// G  Unit Exponent (0,001 V)
  0x46, W(5000),	// G  Physical Maximum (5,000 V)
  0xB1, 0xA2,		// M  Feature (Var,NoPreferred,Volatile)
// DacReport: 18 Byte Feature
  0x85, 2,		// G  Report ID (2)
  0x25, 1,		// G  Logical Maximum (1)
  0x75, 1,		// G  Report Size (1 Bit)
  0x09, 6,		// L  Usage (ein/aus)
  0x79, 6,		// L  String (6 = "aktiver D/A-Wandler")
  0xB1, 0x02,		// M  Feature (Var,NoPreferred)
  0x27, D(0xFFC0L),	// G  Logical Maximum (65472 = 64*0x3FF)
  0x75, 16,		// G  Report Size (16 Bit)
  0x09, 7,		// L  Usage (Sollwert)
  0x79, 7,		// L  String (7 = "Sollwert")
  0xB1, 0x02,		// M  Feature (Var,NoPreferred)
 0xC0,			// M End Collection
// Die Analogwerte hinten erspart es,
// die physikalischen Größen löschen zu müssen
};

// Device descriptor
static const byte PROGMEM DeviceDesc[] ={
 18,		// bLength
 1,		// descriptor type	Device
 W(0x0200),	// USB version supported
 0,		// USB_CFG_DEVICE_CLASS,
 0,		// USB_CFG_DEVICE_SUBCLASS,
 0,		// protocol
 EP0_SIZE,
 W(0x16C0),	// Voti
 W(0x05DF),	// V-USB: ID für generische HID-Geräte
 W(0x0100),
 1,
 2,
 3,		// erforderlich für o.a. ProductID
 1
};

#define CONFIG_DESC_SIZE (9+9+9+7+7)
#define HID_DESC2_OFFSET  (9+9)

// Configuration descriptor
static const byte PROGMEM ConfigDesc[CONFIG_DESC_SIZE] ={
 9,		// bLength
 2,		// bDescriptorType	Config
 W(CONFIG_DESC_SIZE),	// wTotalLength
 1,		// bNumInterfaces	HID
 1,		// bConfigurationValue	erste und einzige
 0,		// iConfiguration	ohne Text
 0xC0,		// bmAttributes		Selbstversorgt, kein Aufwecken
 100/2,		// MaxPower (in 2 Milliampere)	0 mA

 9,		// bLength
 4,		// bDescriptorType	Interface
 0,		// bInterfaceNumber
 0,		// bAlternateSetting
 2,		// bNumEndpoints	IN+OUT
 3,		// bInterfaceClass	HID
 0,		// bInterfaceSubClass
 0,		// bInterfaceProtocol
 0,		// iInterface		ohne Text


 9,		// bLength
 0x21,		// bDescriptorType
 W(0x0110),	// bcdHID
 0,		// bCountryCode
 1,		// bNumDescriptors
 0x22,		// bDescriptorType
 W(sizeof HidReportDesc),	// wDescriptorLength

 7,		// bLength
 5,		// bDescriptorType	Endpoint
 0x81,		// bEndpointAddress	1 IN
 0x03,		// bmAttributes		Interrupt
 W(64),		// wMaxPacketSize
 10,		// bInterval

 7,		// bLength
 5,		// bDescriptorType	Endpoint
 0x02,		// bEndpointAddress	2 OUT
 0x03,		// bmAttributes		Interrupt
 W(64),		// wMaxPacketSize
 10,		// bInterval
};

// Alle String-Deskriptoren (deutsch) hintereinander
static const wchar_t PROGMEM strLang[] =
// High-Teil = DTYPE_String, Low-Teil = (String-Länge+1)*2	
 L"\x0304"	L"\x0407"			// (0) Länge 1: deutsch
 L"\x032C"	L"TU Chemnitz, Enas+ZfM"	// (1) Länge 21
 L"\x032C"	L"STS Multiplex: Gasbox"	// (2) Länge 21
 L"\x0330"	L"heha@hrz.tu-chemnitz.de"	// (3) Länge 23
 L"\x0316"	L"Neuer Wert"			// (4) Länge 10
 L"\x0310"	L"Istwert"			// (5) Länge 7
 L"\x0328"	L"Aktiver D/A-Wandler"		// (6) Länge 19
 L"\x0312"	L"Sollwert"			// (7) Länge 8
 L"\x032C"	L"Digitaler npn-Ausgang"	// (8) Länge 21
;

/********
 * Kode *
 ********/

// Aufruf wenn OTG-Pad Spannung führt
static void usbConnect() {
#ifdef DEBUG
 PORTD|=0x01;		// Ausgang 6
#endif
// PLLFRQ = 0x6A;		// Lt. Datenblatt optimale PLL-Konfiguration für 5 V
// USBINT = 0;
 UHWCON = 0x01;		// Spannungsregler aktivieren (Bit 0 — nicht bei AT90USB162)
// USBCON = 0;		// USB-Block rücksetzen, deaktiviert Pullup-Widerstand
// _delay_ms(150);
 USBCON = 0xB1;		// USB aktivieren, noch ohne Takt (so erforderlich)
 PLLCSR = 0x12;		// PLL: Takt/2, aktivieren (Bits anders als bei AT90USB162)
 while (!(PLLCSR&1));	// warte bis PLL bereit
 USBCON = 0x91;		// USB-Takt anlegen (Bit 5 — OTG-Pad nicht bei AT90USB162)
 UDCON  = 0;		// Pullup-Widerstand aktivieren (Bit 0)
 UDINT  = 0;		// alle Interrupts löschen
 usbCfg = 0;
// UDIEN  = 0x21;		// Interrupts zulassen
}

static void usbDisconnect() {
#ifdef DEBUG
 PORTD&=~0x10;		// Digitalausgang 8 (konfiguriert)
 PORTD&=~0x02;		// Digitalausgang 7 (nicht-schlafend)
 PORTD&=~0x01;		// Digitalausgang 6 (unter Spannung)
#endif
 USBCON = 0x11;		// Kein USB, nur noch OTG-Pad
 PLLCSR = 0x10;		// PLL abschalten
 UHWCON = 0;		// kein USB-Spannungsregler
 UDINT  = 0x01;
 usbCfg = 0;
// UDIEN  = 0;		// keine Interrupts
}


// Misc functions to wait for ready and send/receive packets
static void usb_send_in(void) {
 UEINTX = ~(1<<TXINI);		// Bit 0 löschen = IN-Paket senden
}
static byte usb_wait_in_ready(void) {
 byte i;
 do i=UEINTX;	// wait for host ready for IN packet
 while (!(i & (1<<TXINI | 1<<RXOUTI)));
 return i & 1<<RXOUTI;
}
/*
static void usb_wait_receive_out(void) {
 while (!(UEINTX & (1<<RXOUTI))) ;
}
*/

// immer 64 Byte = volles Paket; immer aus RAM
bool usbEp1Send(const void*p) {
 UENUM=1;
 if (UEINTX&0x01) return false;		// FIFO ist voll? Kann nicht absenden!
 UEINTX=0xA0;		// Alle Interrupts quittieren
 const byte*a=(const byte*)p;
 while (UEINTX&0x20) UEDATX=*a++;	// Daten in FIFO stecken solange RWAL gesetzt ist
 UEINTX=0;		// Paket voll: FIFO umschalten
 return true;
}

// immer 64 Byte = volles Paket
bool usbEp2Recv(void*p) {
 UENUM=2;
 if (!(UEINTX&0x04)) return false;	// Kein RXOUTI? Keine Daten vorhanden!
 UEINTX=0xA0;		// Alle Interrupts quittieren
 byte*a=(byte*)p;
 while (UEINTX&0x20) *a++=UEDATX;	// Daten aus FIFO herauslesen solange RWAL gesetzt ist
 UEINTX=0;		// Paket wurde abgeholt: FIFO umschalten
 return true;
}

static unsigned wLength;		// wLength des letzten SETUPDAT-Pakets

void usbEp0Send(const void*addr, int len) {
 const byte*a=(const byte*)addr;
 bool zlp=false;
 if ((unsigned)len>wLength) {len=wLength; zlp=true;}	// kürzen
 byte n;
 do{
  if (usb_wait_in_ready()) return;	// abort at OUT packet
  n=len>EP0_SIZE?EP0_SIZE:(byte)len;	// send IN packet
  if (n) asm volatile(
"	mov	r1,%2	\n"	// r1 = Zählregister
"1:	sbrc	r2,6	\n"
"	 lpm	r0,Z+	\n"	// vom Flash wenn Bit 6 gesetzt
"	sbrs	r2,6	\n"
"	 ld	r0,Z+	\n"	// vom RAM wenn Bit 6 gelöscht
"	sts	%1,r0	\n"
"	dec	r1	\n"
"	brne	1b	\n"	// r1 ist nachher ordnungsgemäß Null
:"+z"(a):"m"(UEDATX),"r"(n));	// a wird erhöht und ist Ein- und Ausgang ("+")
  usb_send_in();
 }while (len-=n || zlp && n==EP0_SIZE);
}

int usbEp0Recv(void*addr, int len) {
// TODO
 return len;
}

static void usbPollEP0() {
 UENUM = 0;
 uint8_t ueintx=UEINTX;
 if (ueintx&0x08) {			// Setup angekommen?
  byte len;
  const byte *addr;
//SETUPDAT-Paket in 8 handliche 8-Bit-Register einlesen
  byte bmRequestType = UEDATX;
  byte bRequest= UEDATX;
  byte wValueL = UEDATX;
  byte wValueH = UEDATX;
#ifdef DEBUG
UDR1=bRequest==6?wValueH+'@':bRequest+'0';
#endif
  byte wIndexL = UEDATX;
  byte wIndexH __attribute__((unused)) = UEDATX;
  byte wLengthL= UEDATX;
  byte wLengthH= UEDATX;
  UEINTX=ueintx&0xF3;			// Interrupt jetzt erst löschen!!
  wLength=wLengthH<<8|wLengthL;		// globalen Längenzähler setzen

  switch (bmRequestType) {

   case 0x00: switch (bRequest) {	// out, device, device
   
    case 1:				// CLEAR_FEATURE
    case 3: {				// SET_FEATURE
     if (wValueL==1) {			// Feature „Remote Wakeup“
      usb_send_in();			// Null-Byte-IN-Paket absenden
      return;
     }
    }break;
    
    case 5: {				// SET_ADDRESS
     usb_send_in();			// Null-Byte-IN-Paket absenden
     usb_wait_in_ready();		// warte bis abgeholt
     UDADDR = wValueL|0x80;		// Adresse jetzt setzen
    }return;

    case 9: {				// SET_CONFIGURATION
     if (wValueL<2) {
      usb_send_in();			// Null-Byte-IN-Paket absenden
      usbCfg=wValueL;			// setzt Alternate Settings zurück
#ifdef DEBUG
      PORTD&=~0x10;
      if (wValueL) PORTD|=0x10;		// Digitalausgang 8
#endif
      if (wValueL) {
       UENUM  = 1;
       UECONX = 1;
       UECFG0X= 0xC1;			// Interrupt-IN-Endpoint
       UECFG1X= 0x36;			// 64 Byte doppelt gepuffert
       UENUM  = 2;
       UECONX = 1;
       UECFG0X= 0xC0;			// Interrupt-OUT-Endpoint
       UECFG1X= 0x36;			// 64 Byte doppelt gepuffert
//       UEIENX = 0x04;			// Interrupt bei eingehenden OUT-Daten
      }
      UERST = 0x7E;			// alle Endpoints außer EP0 rücksetzen
      UERST = 0;
      return;
     }
    }break;

   }break;
    
   case 0x01: switch (bRequest) {	// out, device, interface

    case 11: {				// SET_INTERFACE
     usb_send_in();			// Null-Byte-IN-Paket absenden
    }return;

   }break;

   case 0x02: switch (bRequest) {	// out, device, endpoint

    case 1:				// CLEAR_FEATURE
    case 3: {				// SET_FEATURE
     if (!wValueL) {			// Feature 0
      byte i = wIndexL & 0x7F;		// Endpoint
      if (i<=2) {
       usb_send_in();			// Null-Byte-IN-Paket absenden
       UENUM = i;
       if (bRequest&2) {
	UECONX = 0x21;			// Stall setzen
       }else{
	UECONX = 0x19;			// Stall sowie Data-Toggle löschen
	UERST  = 1<<i;			// Puffer und UEINTX löschen
	UERST  = 0;
       }
       return;
      }
     }
    }break;

   }break;

   case 0x21: switch (bRequest) {	// out, class, interface
    case 1: {				// HID_SET_REPORT
     onEp0SetReport(wValueH<<8|wValueL);
    }return;
   }break;


   case 0x80: switch (bRequest) {	// in, device, device
    
    case 0: {				// GET_STATUS
     usb_wait_in_ready();		// (sollte unnötig sein!!)
     UEDATX=1;				// Self-Powered
     UEDATX=0;
     usb_send_in();			// Zwei-Byte-Paket abschicken
    }return;				// Null-Byte-Paket ignorieren
		
    case 6: {				// GET_DESCRIPTOR
     usbCfg|=0x40;			// aus Flash
     switch (wValueH) {
      case 1: addr=DeviceDesc; break;
      case 2: {
       addr=ConfigDesc;
       len=sizeof ConfigDesc;
      }goto gd2;
      case 3: {
       if (wValueL>8) goto stall;
       addr=(const byte*)strLang;	// Zusammenhängende String-Deskriptoren
       if (wValueL) do addr+=pgm_read_byte(addr); while(--wValueL);
      }break;
      default: goto stall;
     }
gd1: len=pgm_read_byte(addr);		// Länge vom Deskriptor nehmen
gd2: usbEp0Send(addr,len);
    }return;

    case 8: {				// GET_CONFIGURATION
     usb_wait_in_ready();
     UEDATX = usbCfg&1;
     usb_send_in();
    }return;

   }break;
    
   case 0x81: switch (bRequest) {	// in, device, iface

    case 6: {				// GET_DESCRIPTOR
     usbCfg|=0x40;
     switch (wValueH) {
      case 0x21: addr=ConfigDesc+HID_DESC2_OFFSET; goto gd1;
      case 0x22: addr=HidReportDesc; len=sizeof HidReportDesc; goto gd2;
     }
    }break;
     
    case 10: {				// GET_INTERFACE
     if (wIndexL<2) {
      usb_wait_in_ready();
      UEDATX=0;
      usb_send_in();
      return;
     }
    }break;

   }break;

   case 0x82: switch (bRequest) {	// in, device, endpoint

    case 0: {				// GET_STATUS
     usb_wait_in_ready();
     byte i=0;
     UENUM = wIndexL;
     if (UECONX & 1<<STALLRQ) i=1;
     UENUM =0;
     UEDATX=i;
     UEDATX=0;
     usb_send_in();
    }return;

   }break;

   case 0xA1: switch (bRequest) {	// in, class, interface
    case 1: {				// HID_GET_REPORT
     usbCfg&=~0x40;			// das Liefern aus RAM vorbereiten
     onEp0GetReport(wValueH<<8|wValueL);
     return;
    }break;

     
   }break;

  }
stall:
  UECONX = 0x21;			// stall
 }
}

void usbPoll() {
// Generellen USB-Interrupt behandeln
 if (USBINT&1) {	// Pegelwechsel am OTG-Pad?
  USBINT=0;
  if (USBSTA&1) usbConnect(); else usbDisconnect();
 }
// USB-Device-Interrupts behandeln
 byte udint=UDINT;	// handliches Register
 UDINT=0x01;		// Interrupts löschen, außer SUSPI (Bit 0): Pad-Idle zulassen
 if (udint&1<<EORSTI) {// Ende USB-Reset
  UENUM  = 0;		// EP0 einrichten
  UECFG0X= 0;		// Control-Endpoint
  UECFG1X= 0x32;	// 64 Byte einfach gepuffert
  UECONX = 1;
  UEINTX = 4;		// Kill Bank IN??
//  UEIENX = 0x08;	// Interrupt bei Setup-Paketempfang (zum Aufwecken der CPU)
  usbCfg = 0;		// unkonfiguriert
#ifdef DEBUG
  PORTD&=~0x10;		// Digitalausgang 8
#endif
 }
 if (udint&0x01) {	// Suspend (Hardware löscht WAKEUPI)
#ifdef DEBUG
  PORTD&=~0x02;		// Digitalausgang 7
#endif
  if (!(USBCON&0x20)) {// USB-Takt läuft noch (FRZCLK gelöscht)?
   USBCON=0xB0;		// USB-Takt anhalten (FRZCLK setzen)
   PLLCSR=0x10;		// PLL anhalten (Bit 1)
//  onSuspend();
//   UDIEN =0x30;		// Wakeup, nicht Suspend
  }
 }
 if (udint&0x10) {	// Wakeup (Hardware löscht SUSPI) — kommt ständig
#ifdef DEBUG
  PORTD|=0x02;		// Digitalausgang 7
#endif
  if (USBCON&0x20) {	// USB-Takt angehalten (FRZCLK gesetzt)?
   PLLCSR=0x12;		// PLL starten
//   onResume();
   while (!(PLLCSR&1));// warte bis PLL eingerastet
   USBCON=0x90;		// USB-Takt anlegen
//   UDIEN =0x21;		// Suspend, nicht Wakeup
  }
 }
 usbPollEP0();
}

#ifndef MYSTARTUP	// RETIs stehen sonst direkt in der Interruptvektortabelle
EMPTY_INTERRUPT(USB_GEN_vect);
EMPTY_INTERRUPT(USB_COM_vect);
// Das funktioniert nur deshalb,
// weil nach RETI mindestens ein Befehl des Hauptprogramms abgearbeitet wird,
// bevor die leere ISR wieder aufgerufen wird.
// Daher werden die Interrupts nach sleep() umgehend gesperrt.
// Hier rächt es sich mal wieder,
// dass die AVR-Architektur kein Wakeup ohne Interrupts kennt.
#endif
Detected encoding: UTF-80